
#include "audio_core.h"
using namespace WtfAudio;
using namespace WtfEngine;

#ifdef __AUDIO_OPENAL__

#include <AL/alut.h>
#include "render.h"

#ifdef _WIN32
#	define AUDIO_DEVICE_STRING	(const ALCchar *)"DirectSound3D"
#else
#	define AUDIO_DEVICE_STRING	NULL
#endif


/**** Sound Sources ****/

BaseSound::BaseSound(tAudioRef& rAudioSample): GcObject(), mrAudioSample(*rAudioSample) {
	AudioMixer::CheckError(true);

	alGenSources(1, &mSource);
	AudioMixer::CheckError(true);

	// Set buffer
	alSourcei(mSource, AL_BUFFER, mrAudioSample->getBuffer());
};

void BaseSound::Play(bool bLoop) {
	// Play the sound
	alSourcePlay(mSource);
};

void BaseSound::Destroy() {
	alSourceStop(mSource);
	alDeleteSources(1, &mSource);
};

StaticWorldSound::StaticWorldSound(tAudioRef& rAudioSample, const tPosition& vPosition)
: BaseSound(rAudioSample) {
	alSourcefv(mSource, AL_POSITION, vPosition.toArray());
	alSource3f(mSource,AL_VELOCITY, 0, 0, 0);
};

/**** Audio Sample ****/

AudioSample::~AudioSample() {
	if(mBuffer) {
		alDeleteBuffers(1, &mBuffer);
	};
};
/*
void AudioSample::Read(const String& sFile) {
	mBuffer = alutCreateBufferFromFile(sFile.c_str());
	if(!mBuffer) {
		throw new std::runtime_error(String("Audio: failed to load file - ") + alutGetErrorString(alutGetError()));
	};
};

WtfEngine::IDataObject * AudioSample::Load(const StringPool::Entry& sName) {
	AudioSample * pSample = new AudioSample(sName);
	pSample->Read(sName);
	return pSample;
};*/


/**** Audio Stream ****/

AudioStream::AudioStream(const WtfAudio::Data::tRef &rData, unsigned int uBufSize)
: mrData(rData), mrBuffer(new WtfEngine::NonCopyBuffer<>(uBufSize)) {
	
};

#define AUDIOSTREAM_NUM_BUFFERS 6

void AudioStream::Play(bool bLoop) {
	ALuint buffers[AUDIOSTREAM_NUM_BUFFERS];

	alGenBuffers(AUDIOSTREAM_NUM_BUFFERS, buffers);
	alGenSources(1, &mSource);
	mbLoop = bLoop;

	for(int i = 0; i < AUDIOSTREAM_NUM_BUFFERS; i++) {
		mrData->getData(mrBuffer);
		alBufferData(buffers[i], mrData->getFormat(), mrBuffer->getData(), mrBuffer->getCount(), mrData->getBitRate());
		AudioMixer::CheckError(true);
	};

	// Relative to listener (i.e. follows camera)
	alSourcei(mSource, AL_SOURCE_RELATIVE, AL_TRUE);

	alSourceQueueBuffers(mSource, AUDIOSTREAM_NUM_BUFFERS, buffers);
	AudioMixer::CheckError(true);
	alSourcePlay(mSource);

	IDynamic::Init();
};

void AudioStream::Stop() {
	int queued;

	alSourceStop(mSource);
    alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued);
    
    while(queued-- > 0) {
        ALuint buffer;
    
        alSourceUnqueueBuffers(mSource, 1, &buffer);
        alDeleteBuffers(1, &buffer);
    }

	alDeleteSources(1, &mSource);
	IDynamic::Destroy();
};

void AudioStream::Update() {
	int iNumProcessed;
	bool bActive;

	alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &iNumProcessed);
	bActive = iNumProcessed > 0;
	while(iNumProcessed-- > 0) {
		ALuint buffer;
		alSourceUnqueueBuffers(mSource, 1, &buffer);
		AudioMixer::CheckError(true);

		if(!mrData->getData(mrBuffer)) {
			if(mbLoop) {
				mrData->Reset();
				mrData->getData(mrBuffer);
			} else {
				alDeleteBuffers(1, &buffer);
				break;
			};
		};

		alBufferData(buffer, mrData->getFormat(), mrBuffer->getData(), mrBuffer->getCount(), mrData->getBitRate());
		AudioMixer::CheckError(true);
		alSourceQueueBuffers(mSource, 1, &buffer);
		AudioMixer::CheckError(true);
	};

	if(bActive && !IsPlaying()) {
		alGetSourcei(mSource, AL_BUFFERS_QUEUED, &iNumProcessed);
		if(iNumProcessed > 0) {
			AudioMixer::GetInstance().Log(AudioMixer::LOG_WARNING, "Stream stopped playing - buffer underflow");
			alSourcePlay(mSource);
			AudioMixer::CheckError(true);
		} else {
			Stop();
		};
	};
};

bool AudioStream::IsPlaying() const {
	int state;
	alGetSourcei(mSource, AL_SOURCE_STATE, &state);
	return (state == AL_PLAYING);
};


/**** Audio Mixer ****/

AudioMixer::AudioMixer(): Task<AudioMixer>(0, "Audio"), Loggable<AudioMixer>("Audio") {

	// Open the default sound device
	mpDevice = alcOpenDevice(AUDIO_DEVICE_STRING);
	if(mpDevice == NULL) {
		throw new std::runtime_error("Audio: failed to open default sound device");
	};

	// Create a context
	mpContext = alcCreateContext(mpDevice, NULL);
	if(mpContext == NULL) {
		throw new std::runtime_error("Audio: failed to create sound output context");
	};

	alcMakeContextCurrent(mpContext);
	alutInitWithoutContext(NULL, NULL);

	ExportScripting();
};

AudioMixer::~AudioMixer() {
	alutExit();
	if(mpContext) alcDestroyContext(mpContext);
	if(mpDevice) alcCloseDevice(mpDevice);
};

void AudioMixer::Run() {
	tDirection vvOri[2];

	// Update listener position to main camera position
	ICamera::tRef rCamera = Renderer::GetInstance().GetCamera();
	alListenerfv(AL_POSITION, rCamera->getPosition().toArray());

	// Set orientation
	rCamera->getDirection(vvOri[0], vvOri[1]);
	vvOri[0] = vvOri[0] - rCamera->getPosition();
	alListenerfv(AL_ORIENTATION, (tScalar *)vvOri);

	// Check for stopped sounds
	
};

bool AudioMixer::CheckError(bool bThrow) {
	String sError;

	switch(alGetError()) {
		case ALC_NO_ERROR:
			return true;
		case ALC_INVALID_VALUE:
			sError = "Invalid Value";
			break;
	};

	if(bThrow) {
		throw std::runtime_error(String("OpenAL: ") + sError);
	};

	return false;
};

#endif
